/*****************************************************************************
 * File:        sim_engine.c
 * Description: This file consists primarily of the main() function for the
 *              simulator. The function is basicaly a loop that reads a
 *              transaction and simulates its flow through the cache/memory
 *              hierarchy.
 *
*****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>
#include <string.h>
#include <string>
#include <math.h>
#include <vector>
#include <exception>
#include <sys/time.h>

using namespace std;

#include "cache.h"
#include "sim_engine.h"
#include "transaction.h"
#include "processor.h"
#include "pepsi_init.h"
#include "pepsi_stats.h"
#include "sharedglobals.h"


// ADWAIT : REMOVE
//#include "nox/router.h"
//#include "nox/shared.h"
//#include "nox/batch.h"
//#include "nox/rank.h"


#define MAX_POOL_SIZE 50


int main_cpusim(int argc, char *argv[])
{
  char cmp_conf_file[200] = "cmp-conf.file";
  
  // ADWAIT : REMOVE
  //char nox_conf_file[200] = "nox-conf.file";
  char trace_conf_file[200] = "trace-conf.file";

  int instruction_passes=0;
  int max_passes=30;

  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  //profile runs just analyzing memory reference streams
  //set profile run to true
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  profile_run=false;
  if(profile_run)
  {
    numcpus = atoi(argv[1]);
    strcpy(benchmark,argv[2]);
    trace_setup(trace_conf_file,benchmark,0,numcpus);
    for(int i = 0; i < numcpus; i++)
      //#proc number, trace file name, reorder buffer size, issue width, commit
      //width, execution width
      processor[i].initProcessor(mp_trace_file_name[i],i,256,3,3,3);

    unsigned iter=0;
    while(finished_processors[0] < numcpus)
    {

      if(!(processor[0].instructions%10000))
        printf("************Summary  %lld**************\n",processor[0].instructions);
      for(int i = 0; i < numcpus; i++)
        if(!processor[i].profile())
          finished_processors[0]++;
      //profile_stats();
    }
    profile_stats();
    print_table();

    exit(1);
  }
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  // start simulations (argument parsing)
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

  int active_cpus=0;
  if(argc == 1)
	  printf("usage <benchmark> <#processors> <cmp-conf.file> <-page-interleaving(optional)>\n");

  //benchmark based
  if(argc >= 2)
    strcpy(benchmark,argv[1]);

  if(argc >= 3)
    active_cpus = atoi(argv[2]);

  if(argc >= 4)
    strcpy(cmp_conf_file,argv[3]);

  if(argc >= 5)
  {
    if(!strcmp(argv[5],"-page-interleaving"))
      bank_map = MEM_PAGE_INTERLEAVING;
  }

  sim_setup(cmp_conf_file);
  if(strstr(benchmark,"mpmix"))
  {
    char buff[20];
    FILE *fp = fopen(benchmark, "r");
    int numProcs = 0;
    int StartProc = 0, EndProc = 0;
    
    printf("Setting up for mpmix\n");
    
    while (fgets(buff, 80, fp))
    {
	if(strstr(buff,"#"))
        continue;
      sscanf(buff,"%s %d\n",benchmark,&numProcs);
      EndProc = StartProc + numProcs;
      trace_setup(trace_conf_file,benchmark,StartProc,EndProc);
      strcpy(buff,"");
      StartProc = EndProc;
    }
  }
  else if(active_cpus < numcpus)
  {
    printf("Setting up for active_cpus < numcpus\n");
    trace_setup(trace_conf_file,benchmark,active_cpus,active_cpus+1);
    strcpy(benchmark,"nop");
    trace_setup(trace_conf_file,benchmark,0,active_cpus);
    trace_setup(trace_conf_file,benchmark,active_cpus+1,numcpus);
  }
  else
  {
    printf("Setting up for single benchmark\n");
    trace_setup(trace_conf_file,benchmark,0,numcpus);
  }

  tag_shift = clevel[LLC].cache[0].tag_shift; 
  tr_pool.reserve(MAX_TR);

  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  //Initialize processor here and fastforward to simpoint
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  for(int i = 0; i < numcpus; i++)
    //#proc number, trace file name, reorder buffer size, issue width, commit
    //width, execution width
  {
    processor[i].initProcessor(mp_trace_file_name[i],i,128,3,3,3);
    processor[i].fastforward();
  }
  findUniqueApps();

  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  //network tracer to produce condensed traces or profile run to anaylze network load
  //traffic pattern etc
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  //for(int i = 0; i < numcpus; i++)
  //processor[i].NetworkTracer(30*MILLION, 0);
   //exit(1);

  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  //statistics interval
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

  instructions_to_simulate = MILLION/100; //100
  max_passes = 100; //300; //500;
  global_clock=0;
  instruction_passes=0;
  if(strstr(mp_trace_file_name[0],"pattern"))
    max_passes = 15;
  //ResetPerNodeBuffOccupany();

  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  //START SIMULATION ENGINE
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  while(!pepsi_end)
  {
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    //1. Simulate Processors
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
      for(int i = 0; i < numcpus; i++)
       processor[i].doCycle();
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    //2. Simulate Caches and Memory
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    //simulate_memory();
    
	//L1 cache simulated in the "doCycle function itself.
	
    
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    //3. Simulate Interconnect
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    if(!SHORT_CIRCUIT)
    {
      if(csim_inj_msgs != num_ejt_msg) {
      // ADWAIT : REMOVE 
      //nox_sim((global_clock + 1)*CLOCK_FREQ_SCALE);
      }
      else {
        sim_clock = (global_clock + 1)*CLOCK_FREQ_SCALE;
      }
    }
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    //Monitor System State for fairness throttling etc
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    /*if(!(global_clock%BUFF_OCCUPANCY_INTERVAL))
    {
      UpdatePerClassBuffOccupancy();
      UpdatePerNodeBuffOccupancy();
    }

    if(ARB_TYPE == PR && age_based_arbitration == false)
    {
      if(!(global_clock%MONITOR_INTERVAL_LENGTH))
        monitorUnfairness();
    }*/
		
    // ADWAIT : REMOVE
    /*
		if(GlobalBatchMode > BATCHING_MODE_GLOBAL)
		{
				//printf("%lld %d\n",global_clock,GlobalCurrentBatchId);
				UpdateBatchCounter();
				UpdateBatchId();
				UpdateOldestBatch();
		}
		if(GlobalRankMode > RANKING_GLOBAL)
				GetRankMetricValues();
    */
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    //4. Update system clock
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    global_clock++;
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    
    //if(!(global_clock%50000))
    //getInstProgress();

    /*if(batching_mode[0] == BATCHING_GLOBAL_TIMEBASED)
    {
      if(num_inj_msg)
        if(CountBatchFlits(OldestCompleteBatch%MAX_BATCH_ID) == 0)
        {
          printf("\n\n*********************************************\n");
          printf("Oldest Batch Update %lld NextBatchID %lld @clock %lld LastBatchId:%lld\n",OldestCompleteBatch, (OldestCompleteBatch+1)%MAX_BATCH_ID, sim_clock, last_batch_id[0]);
          printf("*********************************************\n\n");
          OldestCompleteBatch++;
        }

      //for(int i=0; i<MAX_BATCH_ID; i++)
      //p = CountBatchFlits(i)
    }*/


    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    //Upadate Complete
    /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
    if(DEBUG)
      dump_state();

    if(finished_processors[instruction_passes] == numcpus)
    {
      /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
      //5. Statistics per million instructions of each processor
      /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
      //ASIT:Uncomment below
      //for(int i = 0; i < numcpus; i++)
      //  processor[i].printOracleStats(instruction_passes%max_passes, max_passes);
      //ASIT: end comment
      
      //////Single App Profile Starts
      //for(int i = 0; i < numcpus; i++)
      //  processor[i].printCumulativeStats(instruction_passes%MAX_PASS);
      //////Single App Profile Ends
        
      //getIPCMatrix(instruction_passes%MAX_PASS);
      //getStallMatrix(instruction_passes%MAX_PASS);
      //getNetworkStatsMatrix(instruction_passes%MAX_PASS);
      //getAppStats(instruction_passes%MAX_PASS);
      
      printf("************Summary for Pass %.3lfMillion instructions **************\n",(double)((instruction_passes+1)*instructions_to_simulate)/1000000.0);
     
      for(int i = 0; i < numcpus; i++)
          processor[i].getStats(instruction_passes);
      
      if(!SHORT_CIRCUIT)
        {
		//Commented by Nachi
          //for(int i = 0; i < numcpus; i++)
           // processor[i].getNetworkStats(instruction_passes);
        }
      else
         printf("injection rate:%.2lf\n",100*(csim_inj_msgs/((double)(numcpus + num_llc_banks)*global_clock)));

      //Network stats
      if(!SHORT_CIRCUIT)
         //sim_result();
	  //Cache Stats
		 print_stats();
      
      getAvgStats(instruction_passes);

      printf("************Summary for Pass %.3lfM instructions **************\n",(double)((instruction_passes+1)*instructions_to_simulate)/1000000.0);
      
      /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
      //7. Simulation End Condition
      /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
      if(instruction_passes == max_passes)
        pepsi_end=true;

      instruction_passes++;
    }
    
  }

  //if(STAT)
  {
    // print_data_sharing();
     print_stats();
    //if(!SHORT_CIRCUIT)
    //sim_result();
    
  }

  killcachesim();

  // Prompt before exiting for debug
  if(!INTERACTIVE && DEBUG && PRINT)
    getchar();

  if(RECORD_NOC)
    gzclose(rec_noc_fp);

  if(RECORD)
    gzclose(out_fp);

  printf("All done!!! Happily Exiting :)\n");
  return 0;
}

void simulate_memory()
{
  static long long blocked_iter=0;
  bool blocked = true;
  long long id=0;
  TRANSTYPE *temp_tr;
  vector<TRANSTYPE>::iterator temp;

  if(tr_pool.size() == 0)
    return;

  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  // Process each memory transaction
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  for(int index = 0; index < tr_pool.size(); index++)
  {
    try
    {temp_tr = &(tr_pool.at(index));}
    catch(std::exception e) 
    {if(DEBUG) printf(" The location at %d is empty in tr_pool, Exception %s", index, e.what());}

    id = temp_tr->tr_id;

    if(temp_tr->state == EXIT && temp_tr->timestamp <= global_clock)
    {
      determine_new_tr_event(temp_tr);
      //Debug
      /*if(id==1256)
        {print_tr(1256);
         exit(1);
        }*/
      temp_tr = map_transaction(id);
      if(temp_tr->mem_op_id != -1)
        processor[temp_tr->tid].setReady(temp_tr->rob_entry,temp_tr->tr_id);
      
      for(temp = tr_pool.begin(); temp != tr_pool.end(); temp++)
        if(temp->tr_id == id) 
        {tr_pool.erase(temp); break;}
      index--;
      continue;
    }

    if(temp_tr->timestamp <= global_clock )
    {
      determine_new_tr_event(temp_tr);
      temp_tr = map_transaction(id);
    }
    if(temp_tr->timestamp != BLOCKED)
    {
      blocked = false;
      blocked_iter = 0;
    }
  }//for

  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  // Check live lock
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

  if(blocked)
    blocked_iter++;

  if(blocked_iter > 5000)
  {
    blocked_iter = 0;
    char c;
    printf("FATAL ERR SIMULATION IN LIVELOCK !!!!\n");
    DEBUG=1;
    PRINT=1;
    INTERACTIVE=0;
    verbose = 0;
    if(!SHORT_CIRCUIT && csim_inj_msgs > num_ejt_msg)
      printf("Possible network deadlock csim:%lld nox ejt:%lld nox inj:%lld\n",csim_inj_msgs,num_ejt_msg, num_inj_msg);
    printf("Changing to debug mode (y/n) ?\n");
    dump_state();
    print_llc_transactions();
    //  c = getchar();
    //  if(c=='n')
    exit(1);
  }

  return;

}


void dump_state()
{
  printf("\n***********************************SYSTEM STATE AT TIME <%lld>****************************************\n",global_clock);
  if(PRINT )
  {
    printf("TRANSACTION POOL\n");
    print_pool();
    printf("MLC Queues\n");
    print_mlc();   
    printf("LLC Queues\n");
    print_llc();
    printf("MEMORY Queue\n");
    print_mem();
    printf("\n***********************************SYSTEM STATE AT TIME <%lld>****************************************\n",global_clock);
    if(INTERACTIVE)
      getchar();

  }
  printf("**************************************POOL SIZE <%d>*************************************************\n",tr_pool.size());

}

bool mlc_transfer_overflow(int cpuid)
{
  int size = clevel[MLC].cache[cpuid].queue.size();
  // printf("size old %d for MLC%d\n", size, cpuid);
  if(size >= cache_rq_size[MLC])
    return true;

  std::vector<TRANSTYPE>::iterator tr;
  for(tr = tr_pool.begin(); tr != tr_pool.end() ; tr++)
    if(tr->cpuid == cpuid && tr->state == MLC_BUS_TRANSFER)
      size++;
  clevel[MLC].cache[cpuid].tot_rq_size += size;
  clevel[MLC].cache[cpuid].rq_size_samples++;
  //printf("size new %d\n", size);
  if(size < cache_rq_size[MLC])
    return false;
  else
    return true;
}
